using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using FernUniHagen.CompilerExtension.Values;
using Roslyn.Services;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public class CastExpressionAdapter : BlockNodeFactory, IMultiplicityConvertible<CommonSyntaxNode>, IReferencable
    {
        #region
        CastExpressionSyntax _castExpression;
        IDocument _doc;
        BlockSyntax _block;
        StatementSyntax _stmt;
        IdentifierNameSyntax _identifier;
        #endregion

        #region constructors
        public CastExpressionAdapter(CastExpressionSyntax node)
        {
            _castExpression = node;
        }

        public CastExpressionAdapter(IDocument doc, BlockSyntax block, CastExpressionSyntax castExpression, StatementSyntax stmt, IdentifierNameSyntax identifer) : this(castExpression)
        {
            _doc = doc;
            _block = block;
            _stmt = stmt;
            _identifier = identifer;
        }
        #endregion
        /// <summary>
        /// Applys down or upcast depending on the desired cast
        /// </summary>
        /// <returns>list of statements</returns>
        public override StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName)
        {
            var statementsToAdd = new StatementReplacement<CommonSyntaxNode>();
            
            //If this is the case we might deal with a Multiplicity cast. 
            if(_castExpression.Parent is CastExpressionSyntax)
            {
                //TODO: if time left switch to fuly qualified name
                var typeSymbol = SemanticModel.GetTypeInformation(_castExpression.Parent);
                INamedTypeSymbol multiplicityTypeSymbol = SemanticModel.Compilation.GetTypeByMetadataName(Constants.MultiplicityType);

                //If the parent element is a multiplicity we deal with a multiplicity cast. if not we deal with a normal cast

                //We deal with a multiplicity cast - Apply M-Cast rules
                if (typeSymbol.Interfaces.Contains(multiplicityTypeSymbol))
                {
                    _castExpression = (CastExpressionSyntax)_castExpression.Parent;
                    var typeToCastTo = (_castExpression as CastExpressionSyntax).Type;
                    var anyAttributeType = Syntax.ParseName("AnyAttribute");

                    if (anyAttributeType.EquivalentTo(typeToCastTo))
                        statementsToAdd = ApplyUpCast(newVariableName);
                    else
                        statementsToAdd = ApplyDownCast(newVariableName);

                    return statementsToAdd;
                }
            }
           
            //We deal with a type cast - Apply R-TCast Rule
            statementsToAdd = ApplyTypeCast(newVariableName);
            

            return statementsToAdd;

        }

        /// <summary>
        /// Applies the rule R-TCast
        /// </summary>
        /// <param name="newVariableName">the new definition name</param>
        /// <returns>StatementReplacement list</returns>
        private StatementReplacement<CommonSyntaxNode> ApplyTypeCast(string newVariableName)
        {
            var statementsToAdd = new List<StatementSyntax>();
            var newDefinitionIdentifier = Syntax.IdentifierName(newVariableName);
            StatementReplacement<CommonSyntaxNode> replacement = null;
            var variableToCast = (IdentifierNameSyntax)_castExpression.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var declaration = SemanticModel.FindDecleration(variableToCast);
         
            var decl = SyntaxElementFactory.GetDeclaration(declaration, SemanticModel);
          

            var castedGenericType = Utilities.CreateGenericType(_castExpression.Type, decl.GenericName);


            if (decl.MultiplicityType == AttributeType.Any)
            {
                string listDummyVar = VariableCreatorService.GetTempVariable();
                string loopVar = VariableCreatorService.GetTempVariable();

                var dummyListStmt = Syntax.ParseStatement(string.Format("{0} {1} = new {0}();" + Environment.NewLine, castedGenericType, listDummyVar));
                var foreachLoop = Syntax.ParseStatement(string.Format("foreach(var {0} in {1}) {2}.Add(({3}){0}) ;" + Environment.NewLine,
                    loopVar, newDefinitionIdentifier, listDummyVar, _castExpression.Type));
                var newAssignExpression = Syntax.ParseExpression(listDummyVar + ";");

                replacement =
                    new StatementReplacement<CommonSyntaxNode>(new List<StatementSyntax>() { dummyListStmt, foreachLoop }, _castExpression, newAssignExpression);
                
            }
            else
            {
                
                replacement = new StatementReplacement<CommonSyntaxNode>(variableToCast, newDefinitionIdentifier);
                
            }
            return replacement;
        }


        /// <summary>
        /// Implements down cast from multiplicity any to another multiplicity less than any
        /// Basically applys rule R-MCast1. But the implementation was based on the thought of Steimann Document Onward2013
        /// </summary>
        /// <param name="newVariableName">the new definition name</param>
        /// <returns></returns>
        private StatementReplacement<CommonSyntaxNode> ApplyDownCast(string newVariableName)
        {    
            var statementsToAdd = new List<StatementSyntax>();

            
            var typeCast = (CastExpressionSyntax)_castExpression.DescendantNodes().First(n => n is CastExpressionSyntax);

            string typeOfExpression = typeCast.Type.ToString();
            string expressionVariable = VariableCreatorService.GetTempVariable();

            var expressionSyntax = _castExpression.Expression;
            var newResult = VariableCreatorService.GetTempVariable();
            //throw new MultiplicityCastException(\"Multiplicity Exception\")
            var variableDeclaration = Syntax.ParseStatement(string.Format("{0} {1};" + Environment.NewLine, typeOfExpression, newResult));
            var assignExpr = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, expressionVariable, newVariableName));

            //@text : Can not use throw new exception in ?
            //var conditionStmt = Syntax.ParseStatement(
            //    string.Format("{0}.Count == 0 ? null : ({0}.Count == 1 ? {1} = {0}.First() : throw new Exception(\"test\"))" +
            //        Environment.NewLine, expressionVariable, newResult));
            var conditionStmt = Syntax.ParseStatement(string.Format(
                "if({0}.Count == 0) {{ {1} = null; }}{2} " +
                "else if({0}.Count == 1) {{ {3} = {0}.First();{2} }}" +
                "else throw new MultiplicityCastException(\"Multiplicity Exception\");{2}", expressionVariable, newResult, Environment.NewLine, newResult));
            var newAssignExpression = Syntax.ParseExpression(newResult + ";");

            var replacement =
                    new StatementReplacement<CommonSyntaxNode>(
                        new List<StatementSyntax>() { variableDeclaration, assignExpr, conditionStmt },
                        _castExpression, newAssignExpression);
           
            
            return replacement;
        }

        /// <summary>
        /// Applies the Upcast from bare, option etc. to multiplicity any 
        /// Implements the rules for R-MCast2 and R-MCast3
        /// </summary>
        /// <param name="blocksyntax">the block where the cast shall take place</param>
        /// <param name="nodeToReplaceInBlock">the node to replace in the block</param>
        /// <returns>upcasted node</returns>
        private StatementReplacement<CommonSyntaxNode> ApplyUpCast(string newVariableName)
        {
            StatementReplacement<CommonSyntaxNode> replacement = null;
            var statementsToAdd = new List<StatementSyntax>();
           
            var castExpression = ((CastExpressionSyntax)_castExpression.Expression);
            var typeInfo = SemanticModel.GetTypeInfo(castExpression.Expression);


            var variableToCast = (IdentifierNameSyntax)castExpression.Expression.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var declaration = SemanticModel.FindDecleration(variableToCast);
            var decl = SyntaxElementFactory.GetDeclaration(declaration, SemanticModel);
            


            //Apply Rule R-MCast2
            if (decl.MultiplicityType == AttributeType.Any)
            {
                var newExpression = Syntax.ParseExpression(string.Format("new {0}<{1}>() {{{2}}};", castExpression.Type.ToString(),
                    typeInfo.Type, newVariableName));
                replacement =
                   new StatementReplacement<CommonSyntaxNode>(
                       null,
                       _castExpression, newExpression);
                
            }
            //Apply Rule R-MCast3
            else
            {
                string listVariable = VariableCreatorService.GetTempVariable();
                string tempVariable = VariableCreatorService.GetTempVariable();
                var initList = Syntax.ParseStatement(string.Format("{0}<{1}> {2} = new {0}<{1}>();" + Environment.NewLine,
                    castExpression.Type.ToString(), typeInfo.Type, listVariable));
                var storeVariable = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempVariable, newVariableName));
                var condition = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Add({0});" + Environment.NewLine, tempVariable, listVariable));
                var newAssignExpression = Syntax.ParseExpression(listVariable + ";");

                replacement =
                  new StatementReplacement<CommonSyntaxNode>(
                      new List<StatementSyntax>() { initList, storeVariable, condition },
                      _castExpression, newAssignExpression);
             
            }


            return replacement;
        }

        public SyntaxNodeInfo GetReference()
        {
            SyntaxNodeInfo nodeInfo = new SyntaxNodeInfo(_doc, _block, _castExpression, _stmt, _identifier);
            return nodeInfo;
        }
    }
}
